{"componentChunkName":"component---src-templates-blog-post-js","path":"/2026-03-14-terraform-aws-centralised/","result":{"data":{"site":{"siteMetadata":{"title":"Hitarth Asrani","author":"Hitarth Asrani"}},"markdownRemark":{"id":"bc5594f8-84d5-5d24-808a-2221c0c010a8","excerpt":"Recently, I had the opportunity to set up the Terraform Backend on AWS for an organization running several workloads. I realised that there’s been a few changes…","html":"<p>Recently, I had the opportunity to set up the Terraform Backend on AWS for an organization running several workloads. I realised that there’s been a few changes to how the Terraform backend on AWS can be set up.\nOne of the major changes is that Dynamo DB is not needed anymore for state locking, instead Terraform now uses S3 for state locking. While incorporating this into the solution, another requirement became very clear: the Terraform backend needed to be centralised into one account so that the team would not have to maintain separate backends for each environment.</p>\n<p>In this case this was their preference, other teams may prefer to have a backend S3 bucket set up for each account they have on AWS. With the centralised approach, the main issues that arise are:</p>\n<ul>\n<li>Managing cross account permissions/ access for the Terraform backend.</li>\n<li>Being able to utilise exisitng IAM permissions or roles in an existing workload account (to deploy resources via Terraform)</li>\n<li>Managing multiple providers for each different AWS account in Terraform</li>\n</ul>\n<h2>The solution</h2>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/3195c4d3cf3956ede2d3c798fbd5d959/e185b/terraform-shared-backend.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 81.08108108108108%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsTAAALEwEAmpwYAAACEklEQVQ4y5VUy27bMBDU/39KDy2KXooecimQHHpoWsR9BLEdO44dybQelERKfE2XoiSwRgIkBBYUyN3Zmd2lEkTLOTfsilU4fbiCLSQsa6GPfDiznYJVGk4ZWNHBafNfnF/JOaC/csbBOoNjs8K+XKAzbbiPfQnsVYCTs+075M0a9+kVyuYhnFs7+wxM3wKoRI99tsZm/we8qeBieh6w12+RbCEbAZYXSNMMRVFCOwu92kJcXIIyDPodsXw1Q2g7n2mjURA4u14g//YDxXoL9RLDgVVkcw19jaJkbSvAhUCr9bB3pMArcZOy0RI8s5ylLss+jMlobclpnogRMYPvsE94XljPcAiky6nIfsZ85lnKmFkQq5iJT+JB7WhDHMUnx3dfofMaimyaLZ8kzu6/pZTzmWk7sPeX6B8ZVFYSmVBLuTwgMbX0GmErHo2EwvmcxIBejeFiqg9MXg6f4u+OJJO89vsNyo9f0O8OIWBkGJsHnBl7eTY0RFz/QkWx6vAEuUqRFMcTDst7pMsHsH1KT47GhN6ppJp5kMnquh7n1IV6ExHOa4rdIF3twLIM/PeWxkYF/cjvqHtBhi/4+ZrBuw6ibqgRo4+lUrDbIPn2kQA1MeoFmpvPUFU6A05s4vl87segyz3kz0/kqSDuqCmWOiZbiVOWo8qrwdF3cR7yaI9/DFYqGErMUoan7Q4l52gXG/wDux3iwnvLIrkAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"SolutionArch\"\n        title=\"SolutionArch\"\n        src=\"/static/3195c4d3cf3956ede2d3c798fbd5d959/fcda8/terraform-shared-backend.png\"\n        srcset=\"/static/3195c4d3cf3956ede2d3c798fbd5d959/12f09/terraform-shared-backend.png 148w,\n/static/3195c4d3cf3956ede2d3c798fbd5d959/e4a3f/terraform-shared-backend.png 295w,\n/static/3195c4d3cf3956ede2d3c798fbd5d959/fcda8/terraform-shared-backend.png 590w,\n/static/3195c4d3cf3956ede2d3c798fbd5d959/e185b/terraform-shared-backend.png 691w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<p>Let’s walk through the proposed solution and it’s components</p>\n<h3>Central/Shared Account</h3>\n<p>The central account will contain</p>\n<ul>\n<li>“TerraformBackendBucket” - An S3 Bucket that will be used by the Terraform backend\nThe S3 bucket policy will be updated to allow the IAM role for Terraform - TerraformBackendRole to read and write to the bucket.\nie. <code class=\"language-text\">s3:GetObject</code> and <code class=\"language-text\">s3:PutObject</code> permissions.</li>\n<li>\n<p>“TerraformBackendRole” - An IAM Role with permissions to READ and WRITE only to the bucket.\nThe IAM Role’s trust policy should be updated with the following JSON:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">{\n\"Effect\": \"Allow\",\n\"Action\": \"sts:AssumeRole\",\n\"Resource\": [\"arn:aws:iam::&lt;workload1-account-ID>:role/TerraformWorkload1Role\",\n            \"arn:aws:iam::&lt;workload2-account-ID>:role/TerraformWorkload2Role\"\n]\n}</code></pre></div>\n<p>As the workloads increase, the number of resoruces in the trust policy will increase. If you want a more relaxed approach, the below trust policy can be used.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">{\n\"Effect\": \"Allow\",\n\"Action\": \"sts:AssumeRole\",\n\"Resource\": [\"arn:aws:iam::&lt;workload1-account-ID>:root\",\n            \"arn:aws:iam::&lt;workload2-account-ID>:root\"\n]\n}</code></pre></div>\n<p>This allows anyone from the workload accounts WorkloadAccount1 or WorkloadAccount2 to run STS AssumeRole on the TerraformBackendRole to access the shared backend. The use case for this is mainly for ReadOnly users or large teams who want to test using AWS CLI on their local machines. However users will need permissions to assume this role with their credentials. In this usecase, teams will not have additional admin for each new user added to the workload account that needs Terraform access.</p>\n</li>\n</ul>\n<h3>Workload Accounts</h3>\n<p>The workload accounts contain:</p>\n<ul>\n<li>An IAM Role that is used by that workload pipeline to deploy infrastructure.\nLet’s assume the IAM role for a CICD pipeline already exists with permissions to create required resources in the account. In this case we can update the role to have this policy attached:</li>\n</ul>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">Statement: [{...existing statements}{\n    Effect: Allow,\n    Action: \"sts:AssumeRole\",\n    \"Resource\": \"arn:aws:iam::&lt;shared-account-number>:role/TerraformBackendRole\"\n}]</code></pre></div>\n<p>Once this is added, the role should be able to assume the TerraformBackendRole to access the s3 backend bucket.</p>\n<ul>\n<li>Any other workload components like an ec2 instance, etc.</li>\n</ul>\n<h2>Digging deeper into the code</h2>\n<p>On the Terraform side, the backend.tf looks something like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">>>> backend.tf\nterraform {\nbackend \"s3\" {\n    bucket=\"TerraformBackendBucket\"\n    use_lockfile=true\n    key=\"Workload1WebTeamTerraform\"\n    region=\"ap-southeast-2\"\n    encrypt=true\n    assume_role = {\n        role_arn=\"arn:aws:iam::&lt;shared-account-id>:role/TerraformBackendRole\n    }\n}\n}</code></pre></div>\n<p>This automatically tries to run an AssumeRole on TerraformBackendRole in the background. It also allows us to separate backend access from anything the workload needs access to. The main magic is in the last 3 lines, where the ARN for the centralised IAM role is passed to the backend. This is only used for backend state management.\nFinally, we do not need to use any additional aliased AWS providers with the Terraform setup. The backend file is mostly plug and play for any additional workloads in the Workload AWS account.\nBear in mind, the terraform S3 backend path should be updated if many different Terraform codebases - say a) Workload1DataTeamTerraform and b) Workload1WebTeamTerraform - deploy to the same account.</p>\n<h2>Potential Improvements</h2>\n<p>There are some potential improvements for this solution:</p>\n<ul>\n<li>The S3 bucket can be shared across the AWS Organization which may eliminate the need for cross account roles, however, I have not tested this yet.</li>\n<li>Limiting the workload roles to access specific folders on the Terraform state bucket so that other workloads arent accessible by accident.</li>\n<li>MFA Delete on the S3 bucket to prevent the bucket objects from being deleted, explicit Deny on the IAM Role(s) to prevent deletion will also help.</li>\n</ul>\n<h2>TLDR</h2>\n<p>TLDR: In this blog we discussed an approach to centralising AWS Terraform states to one account and accessing it from multiple workload accounts. This usecase can be used for large AWS organizations on AWS that want to centralise Terraform state management to a single AWS account. It utilises cross account role permissions to create a versatile backend.tf template that can be used across workloads once the solution is set up.</p>","frontmatter":{"title":"Terraform X AWS - Setting up a Shared Centralised Backend","date":"March 14, 2026","description":"A how to guide on setting up a shared/centralised backend for Terraform on AWS","videoSrcURL":null,"videoTitle":null}}},"pageContext":{"slug":"/2026-03-14-terraform-aws-centralised/","previous":{"fields":{"slug":"/2024-01-25-aws-ssm-debug/"},"frontmatter":{"title":"Best practices for Debugging AWS Applications"}},"next":null}},"staticQueryHashes":["4123550546","63159454"]}